﻿using System;
using System.Diagnostics;
using System.ComponentModel;
using System.IO;
using System.Net;

namespace MSDNWebcast.Models
{
    /// <summary>
    /// 下载
    /// </summary>
    public class Downloader : INotifyPropertyChanged
    {
        #region Constructors

        protected Downloader()
        {
            this.State = DownloadState.WaitToDownload;
        }

        /// <summary>
        /// 下载
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="url"></param>
        public Downloader(string filePath, string url)
            : this()
        {
            this.FilePath = filePath;
            this.Url = url;
            this.StartedTime = DateTime.Now.ToShortDateString();
        }

        #endregion

        #region Fields

        /// <summary>
        /// 下载速度
        /// </summary>
        private float m_DownloadSpeed;

        /// <summary>
        /// 已下载大小
        /// </summary>
        private long m_DownloadedSize;

        /// <summary>
        /// 下载状态
        /// </summary>
        private DownloadState m_State;

        /// <summary>
        /// 下载出错
        /// </summary>
        private Exception m_ErrorException;

        /// <summary>
        /// 下载
        /// </summary>
        private BackgroundWorker m_Worker;

        #endregion

        #region Properties

        /// <summary>
        /// 下载文件路径
        /// </summary>
        public string FilePath { get; private set; }

        /// <summary>
        /// 开始时间
        /// </summary>
        public string StartedTime { get; set; }

        /// <summary>
        /// 下载文件Url地址
        /// </summary>
        public string Url { get; private set; }

        /// <summary>
        /// 下载速度
        /// </summary>
        public float DownloadSpeed
        {
            get { return m_DownloadSpeed; }
        }

        /// <summary>
        /// 文件大小
        /// </summary>
        public long FileSize { get; private set; }

        /// <summary>
        /// 已下载文件大小
        /// </summary>
        public long DownloadedSize
        {
            get { return m_DownloadedSize; }
        }

        /// <summary>
        /// 文件大小信息
        /// </summary>
        public string FileSizeInfo
        {
            get
            {
                if (FileSize <= 0) return "未知大小";
                var kb = (float)FileSize / 1024f;
                if ((kb / 1024f) < 1f)
                    return String.Format("总共 {0}KB", kb.ToString("0.00"));
                else
                {
                    return String.Format("总共 {0}MB", (kb / 1024f).ToString("0.00"));
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public string DownloadedSizeInfo
        {
            get
            {
                var kb = (float)DownloadedSize / 1024f;
                if ((kb / 1024f) < 1f)
                    return String.Format("已下载 {0}KB", kb.ToString("0.00"));
                else
                {
                    return String.Format("已下载 {0}MB", (kb / 1024f).ToString("0.00"));
                }
            }
        }

        /// <summary>
        /// 下载状态
        /// </summary>
        public DownloadState State
        {
            get { return m_State; }
            private set
            {
                if (m_State != value)
                {
                    m_State = value;
                    OnPropertyChanged("State");
                }
            }
        }

        /// <summary>
        /// 下载错误
        /// </summary>
        public Exception ErrorException
        {
            get { return m_ErrorException; }
            private set
            {
                if (value != m_ErrorException)
                {
                    m_ErrorException = value;
                    OnPropertyChanged("ErrorException");
                }
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// 异步下载文件
        /// </summary>
        public void ProcessAsync()
        {
            if (this.State == DownloadState.Downloading) return;
            m_Worker = new BackgroundWorker {WorkerReportsProgress = true, WorkerSupportsCancellation = true};
            m_Worker.RunWorkerCompleted += ProcessCompleted;
            m_Worker.ProgressChanged += ProgressChanged;
            m_Worker.DoWork += Download;
            m_Worker.RunWorkerAsync();
            this.State = DownloadState.Downloading;
        }

        /// <summary>
        /// 取消下载
        /// </summary>
        public void CancelProcess()
        {
            if (m_Worker != null)
                m_Worker.CancelAsync();
        }

        /// <summary>
        /// 下载
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void Download(object sender, DoWorkEventArgs e)
        {
            var worker = (BackgroundWorker) sender;

            //打开上次下载的文件或新建文件 
            long lStartPos;
            FileStream fs;
            if (File.Exists(FilePath))
            {
                fs = File.OpenWrite(FilePath);
                lStartPos = fs.Length;
                fs.Seek(lStartPos, SeekOrigin.Current); //移动文件流中的当前指针 
            }
            else
            {
                var directory = Path.GetDirectoryName(FilePath);
                if (!String.IsNullOrEmpty(directory) && !Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }
                fs = new FileStream(FilePath, FileMode.Create);
                lStartPos = 0;
            }
            this.m_DownloadedSize = lStartPos;
            HttpWebResponse response = null;
            Stream ns = null;
            try
            {
                var request = (HttpWebRequest)WebRequest.Create(Url);
                if (lStartPos > 0)
                    request.AddRange((int)lStartPos); //设置Range值 
                request.Proxy = WebRequest.DefaultWebProxy;
                request.Proxy.Credentials = CredentialCache.DefaultCredentials;
                response = (HttpWebResponse)request.GetResponse();
                this.FileSize = response.ContentLength;
                ns = response.GetResponseStream();

                var watch = new Stopwatch();
                var nbytes = new byte[512];
                watch.Start();

                int nReadSize = ns.Read(nbytes, 0, 512);
                while (nReadSize > 0)
                {
                    watch.Stop();
                    if (watch.ElapsedMilliseconds > 0)
                    {
                        m_DownloadSpeed = nReadSize / watch.ElapsedMilliseconds;
                    }
                    this.m_DownloadedSize += nReadSize;
                    this.m_Worker.ReportProgress(0);

                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;
                        break;
                    }
                    watch.Start();
                    fs.Write(nbytes, 0, nReadSize);
                    nReadSize = ns.Read(nbytes, 0, 512);
                }
                watch.Stop();
            }
            catch (Exception exception)
            {
                throw;
            }
            finally
            {
                fs.Close();
                if (response != null)
                    response.Close();
                if (ns != null)
                    ns.Close();
            }
        }

        /// <summary>
        /// 触发下载的速度和状态改变
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            OnPropertyChanged("DownloadSpeed");
            OnPropertyChanged("DownloadedSizeInfo");
            OnPropertyChanged("DownloadedSize");
        }

        /// <summary>
        /// 完成下载
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void ProcessCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                if (File.Exists(this.FilePath))
                    File.Delete(this.FilePath);
                this.State = DownloadState.Canceled;
            }
            else if (e.Error != null)
            {
                if (File.Exists(this.FilePath))
                    File.Delete(this.FilePath);
                this.State = DownloadState.Error;
                this.ErrorException = e.Error;
            }
            else
            {
                this.State = DownloadState.Finished;
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// 触发事件
        /// </summary>
        /// <param name="propertyName"></param>
        public void OnPropertyChanged(string propertyName)
        {
            VerifyPropertyName(propertyName);

            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void VerifyPropertyName(string propertyName)
        {
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                string msg = "Invalid property name: " + propertyName;
                throw new Exception(msg);
            }
        }
        #endregion

        /// <summary>
        /// 
        /// </summary>
        public void Resume()
        {
            this.State = DownloadState.WaitToDownload;
        }
    }
}
